Expand description
Universal shader translator.
The central structure of the crate is Module
. A Module
contains:
-
Function
s, which have arguments, a return type, local variables, and a body, -
EntryPoint
s, which are specialized functions that can serve as the entry point for pipeline stages like vertex shading or fragment shading, -
Constant
s andGlobalVariable
s used byEntryPoint
s andFunction
s, and -
Type
s used by the above.
The body of an EntryPoint
or Function
is represented using two types:
-
An
Expression
produces a value, but has no side effects or control flow.Expressions
include variable references, unary and binary operators, and so on. -
A
Statement
can have side effects and structured control flow.Statement
s do not produce a value, other than by storing one in some designated place.Statements
include blocks, conditionals, and loops, but also operations that have side effects, like stores and function calls.
Statement
s form a tree, with pointers into the DAG of Expression
s.
Restricting side effects to statements simplifies analysis and code generation.
A Naga backend can generate code to evaluate an Expression
however and
whenever it pleases, as long as it is certain to observe the side effects of all
previously executed Statement
s.
Many Statement
variants use the Block
type, which is Vec<Statement>
,
with optional span info, representing a series of statements executed in order. The body of an
EntryPoint
s or Function
is a Block
, and Statement
has a
Block
variant.
If the clone
feature is enabled, Arena
, UniqueArena
, Type
, TypeInner
,
Constant
, Function
, EntryPoint
and Module
can be cloned.
Arenas
To improve translator performance and reduce memory usage, most structures are
stored in an Arena
. An Arena<T>
stores a series of T
values, indexed by
Handle<T>
values, which are just wrappers around integer indexes.
For example, a Function
’s expressions are stored in an Arena<Expression>
,
and compound expressions refer to their sub-expressions via Handle<Expression>
values. (When examining the serialized form of a Module
, note that the first
element of an Arena
has an index of 1, not 0.)
A UniqueArena
is just like an Arena
, except that it stores only a single
instance of each value. The value type must implement Eq
and Hash
. Like an
Arena
, inserting a value into a UniqueArena
returns a Handle
which can be
used to efficiently access the value, without a hash lookup. Inserting a value
multiple times returns the same Handle
.
If the span
feature is enabled, both Arena
and UniqueArena
can associate a
source code span with each element.
Function Calls
Naga’s representation of function calls is unusual. Most languages treat
function calls as expressions, but because calls may have side effects, Naga
represents them as a kind of statement, Statement::Call
. If the function
returns a value, a call statement designates a particular Expression::CallResult
expression to represent its return value, for use by subsequent statements and
expressions.
Expression
evaluation time
It is essential to know when an Expression
should be evaluated, because its
value may depend on previous Statement
s’ effects. But whereas the order of
execution for a tree of Statement
s is apparent from its structure, it is not
so clear for Expressions
, since an expression may be referred to by any number
of Statement
s and other Expression
s.
Naga’s rules for when Expression
s are evaluated are as follows:
-
Constant
expressions are considered to be implicitly evaluated before execution begins. -
FunctionArgument
andLocalVariable
expressions are considered implicitly evaluated upon entry to the function to which they belong. Function arguments cannot be assigned to, andLocalVariable
expressions produce a pointer to the variable’s value (for use withLoad
andStore
). Neither varies while the function executes, so it suffices to consider these expressions evaluated once on entry. -
Similarly,
GlobalVariable
expressions are considered implicitly evaluated before execution begins, since their value does not change while code executes, for one of two reasons:-
Most
GlobalVariable
expressions produce a pointer to the variable’s value, for use withLoad
andStore
, asLocalVariable
expressions do. Although the variable’s value may change, its address does not. -
A
GlobalVariable
expression referring to a global in theAddressSpace::Handle
address space produces the value directly, not a pointer. Such global variables hold opaque types like shaders or images, and cannot be assigned to.
-
-
A
CallResult
expression that is theresult
of aStatement::Call
, representing the call’s return value, is evaluated when theCall
statement is executed. -
Similarly, an
AtomicResult
expression that is theresult
of anAtomic
statement, representing the result of the atomic operation, is evaluated when theAtomic
statement is executed. -
All other expressions are evaluated when the (unique)
Statement::Emit
statement that covers them is executed.
Now, strictly speaking, not all Expression
variants actually care when they’re
evaluated. For example, you can evaluate a BinaryOperator::Add
expression
any time you like, as long as you give it the right operands. It’s really only a
very small set of expressions that are affected by timing:
-
Load
,ImageSample
, andImageLoad
expressions are influenced by stores to the variables or images they access, and must execute at the proper time relative to them. -
Derivative
expressions are sensitive to control flow uniformity: they must not be moved out of an area of uniform control flow into a non-uniform area. -
More generally, any expression that’s used by more than one other expression or statement should probably be evaluated only once, and then stored in a variable to be cited at each point of use.
Naga tries to help back ends handle all these cases correctly in a somewhat
circuitous way. The ModuleInfo
structure returned by Validator::validate
provides a reference count for each expression in each function in the module.
Naturally, any expression with a reference count of two or more deserves to be
evaluated and stored in a temporary variable at the point that the Emit
statement covering it is executed. But if we selectively lower the reference
count threshold to one for the sensitive expression types listed above, so
that we always generate a temporary variable and save their value, then the
same code that manages multiply referenced expressions will take care of
introducing temporaries for time-sensitive expressions as well. The
Expression::bake_ref_count
method (private to the back ends) is meant to help
with this.
Expression
scope
Each Expression
has a scope, which is the region of the function within
which it can be used by Statement
s and other Expression
s. It is a validation
error to use an Expression
outside its scope.
An expression’s scope is defined as follows:
-
The scope of a
Constant
,GlobalVariable
,FunctionArgument
orLocalVariable
expression covers the entireFunction
in which it occurs. -
The scope of an expression evaluated by an
Emit
statement covers the subsequent expressions in thatEmit
, the subsequent statements in theBlock
to which thatEmit
belongs (if any) and their sub-statements (if any). -
The
result
expression of aCall
orAtomic
statement has a scope covering the subsequent statements in theBlock
in which the statement occurs (if any) and their sub-statements (if any).
For example, this implies that an expression evaluated by some statement in a
nested Block
is not available in the Block
’s parents. Such a value would
need to be stored in a local variable to be carried upwards in the statement
tree.
Modules
- Backend functions that export shader
Module
s into binary and text formats. - Frontend parsers that consume binary and text shaders and load them into
Module
s. Module
processing functionality.- Shader validator.
Structs
- An arena holding some kind of component (e.g., type, constant, instruction, etc.) that can be referenced.
- Memory barrier flags.
- A code block is a vector of statements, with maybe a vector of spans.
- Constant value.
- Early fragment tests.
- The main function for a pipeline stage.
- A function defined in the module.
- A function argument.
- A function result.
- Variable defined at module level.
- A strongly typed reference to an arena item.
- Variable defined at function level.
- Shader module.
- A strongly typed range of handles.
- Pipeline binding information for global resources.
- A human-readable representation for a span, tailored for text source.
- A source code span, used for error reporting.
- Flags describing an image.
- Member of a user-defined structure.
- A case for a switch statement.
- A data type declared in the module.
- An arena whose elements are guaranteed to be unique.
- Wrapper class for
Error
, augmenting it with a list ofSpanContext
s.
Enums
- Addressing space of variables.
- Size of an array.
- Function on an atomic value.
- Operation that can be applied on two values.
- Describes how an input/output variable is to be bound.
- Built-in inputs and outputs.
- Enables adjusting depth without disabling early Z.
- Additional information, dependent on the kind of constant.
- Axis on which to compute a derivative.
- An expression that can be evaluated to obtain a value.
- Sub-class of the image type.
- The number of dimensions an image has.
- Type of an image query.
- The interpolation qualifier of a binding or struct field.
- Built-in shader function for math.
- Built-in shader function for testing relation between values.
- Sampling modifier to control the level of detail.
- The sampling qualifiers of a binding or struct field.
- Primitive type for a scalar.
- A literal scalar value, used in constants.
- Stage of the programmable pipeline.
- Instructions which make up an executable block.
- Image storage format.
- The value of the switch case.
- Component selection for a vector swizzle.
- Enum with additional information, depending on the kind of type.
- Operation that can be applied on a single value.
- Number of components in a vector.
Constants
- Width of a boolean type, in bytes.
Type Definitions
- Number of bytes per scalar.
- Hash map that is faster but not resilient to DoS attacks.
- Hash set that is faster but not resilient to DoS attacks.
- A source code span together with “context”, a user-readable description of what part of the error it refers to.